package md.specialEqp;

import com.alibaba.fastjson2.JSON;
import com.querydsl.core.annotations.QueryInit;
import graphql.schema.DataFetchingEnvironment;
import lombok.*;
import md.cm.flow.ApprovalStm;
import md.specialEqp.inspect.Isp;
import md.specialEqp.inspect.ReportType_Enum;
import md.system.User;
import org.fjsei.yewu.dto.RepLink;
import org.fjsei.yewu.filter.FlowStat;
import org.fjsei.yewu.filter.SimpleReport;
import org.fjsei.yewu.filter.Uunode;
import org.fjsei.yewu.util.Tool;

import jakarta.persistence.*;
import java.util.Date;
import java.util.UUID;

/*
主报告，分项报告 REP_TYPE, 证书或组合第一页的提纲形式{点击进入很多子报告}。非结构化json转成文档。
实体类不可搞 interface： ？可能死循环, 返回结果集只能使用接口/不能做实体转换。
 @Id采用GenerationType.SEQUENCE,共用sequenceName要确保旧数据失效清除周期一致，ID若要循环到最大极限值回到1起点后了若还有小数字ID就麻烦了。
 mySQL修改Id自增起点：  select next_val as id_val from SEQUENCE_COMMON  for update；   update SEQUENCE_COMMON set next_val= ?  where next_val=?
 Hibernate提供@GenericGenerator(strategy = "uuid")不能用，Long与String不兼容；只好麻烦点，旧数据维护要看id设置找底层数据库支持修改next_val:initialValue。
 @Lob字段小心：可能造成见建表失败，mysql和Oracle还表现不一致。
*/

/** 原始记录+报告的数据，快照信息,分项报告独立流转。
 * 分项报告或主报告{目录页报告内嵌在主报告上}，每种REP_TYPE都不同的。
 * 流转审核打印等人员状态日期。
 * 实际对应旧平台TB_TASK_TO_ISPPROJ{派工关联和关键信息,MAIN_FLAG主报告标识1：主报告;0分项报告，REP_TYPE检验项目}
 * 每个主子报告都有，TB_ISP_SUBPROJ实际等同于TB_ISP_MGE主表的！，分项子报告/主=分离继承/组合显示，把流转独立。
 * 流程实际该放这里：WF_TODO/TB_ISP_SUBPROJ.FLOW_IMPCOD/WF_FLOW_IMP.FLOW_IMPID加流程；
 * 旧平台TB_ISP_DET实际该放这里的检验时刻参数。
 * 设备eqp；协议prot；任务task；账务记录actr；业务记录isp；报告report; 管道单元plunit。
 * task: eqp =1: n/0;  无关联设备的业务；
 * prot: task=1: n;  单个 task 业务类型单一，检验大部门归属单一个。
 * actr: task=n: n;  单个 task还可以分裂开多条的账务记录。
 * isp: eqp =n: 1;  单个task发起的isp一次只能分别为每一个eqp分配有单独的isp，也就是isp只能做1个eqp或无关联eqp;
 * isp: report=1: n; 报告/分项报告/证书；report也可独立流转审批，isp有总的审批。
 * plunit可独立多选选择；单1个 task 业务可挂接多个管道eqp，每一条管道还可以分别选择其中部分的plunit。
 * 管道单元设备表pipeEqpunit会登记3个业务(监督检验、全面检验，在线检验)上一次的Isp用以过滤管道单元选择；isp关联eqp但是对于plunit不做关联；
 * 管道单元未终结Isp: 当前管道单元最新已生成task关联关系表[任务锁], 任务锁主人=task,直接在pipeEqpunit实体增设'当前task'=任务锁；
 * 有了某个plunit的业务opetype，就锁定新增加任务的源头，任务完成才能对管道单元解除新增任务锁。
 * report批准了[再也不能改报告了] -> 账务结清确认[催收] -> isp终结确认[发送凭证快递]  -> 任务完成确认[单task多个eqp都完成了]->管道单元任务锁解除 。
 * report形式可多样，web网页内容链接的报告，上传文件存储pdf/excl/doc/图片报告，允许isp没有关联实际report仅提供报告号和来源地文字说明或外部html链接;
 * report :上传文件File关联 =1:N。 把isp作为监察视察的入口对象。
 * 数据和文件的淘汰删除时机：从关联度底的实体开始清理，重要性程度，过期时间多长。
 * Task直接挂接收费账务/协议/Isp/工分/前端入口作业列表/工作成果统计/任务前置导入生成机制/分配派工。
 * Isp直接挂接报告/分项报告/设备/Task/监察或第三方直接链接Isp历史记录。
 * 报告编制环节还需要流转审批；Report不能直接脱离,对于历史报告和查看已经做好的报告的情形是可以的;但是流转还未完结的报告就无法独立分离?。
 * Report存储年限等同于Isp保留物理存储的年限，按重要性分别对待预计20年过期删除。
 */

@Builder(toBuilder=true)
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Getter
@Setter
//@TypeDef(name = "json", typeClass = JsonStringType.class)需要包'com.vladmihalcea:hibernate-types-55'
public class Report implements SimpleReport, Uunode, FlowStat {
    //冷数据，超过年限不再用(垃圾)数据，3种数据处置；不能无限度增加记录，旧平台TB_EQP_MGE=70.9W条;TB_TASK_MGE=311W条;TB_ISP_MGE=228W条;
    //报告的后端数据存储和前端模板都有版本号，配套数据版本一致兼容。
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;
    //需要仰仗前端回传当前编辑器看见的版本序号。
    @Version
    private int  version;
    //OPE_TYPE配合BUSI_TYPE法定1/=2委托业务的；来敲定的报告类型REP_TYPE
    //而检验范畴ISP_TYPE可以省略掉：机电 承压类->只是给科室分配/发票会计用，挑选列表大的分类大归类的/统计上分家。
    /**
     * 成果报告媒体形式： Web网页报告, 单文件Pdf/Excel， 多个附件files，, String，纯粹URL/URI。
     * 最极端的报告成果形式：有可能只有一点文字说明,能收到钱就也算业务成果。
     * Report可能存储分离出去到MogoDB库(URL+独立服务模式)； 不同的微服务需要单独的数据库。
     * web形式报告：目录式主报告，多个子报告链接。子报告的数据存储独立拆分。
     * 分项报告遇到相同模板的分项有很多项的，可多个项聚合[单分项报告]，或拆分几个分项链接子报告：测厚子报告[单元1,2,3]，测厚子报告[单元4,5,6]。
     这里data+snapshot两个是为 统一后端集成的 web形式报告的 模式准备的，其它模式报告不需要这2字段。
     这个组合<type + path(平台/pathname?) +Isp.no +ispDate+ ispu>就能支持实际报告的可能的外部平台URL情况下的@加载读取。
     正常多个分项报告应当和主报告的type类型保持相同！
     */
    @Builder.Default
    private ReportType_Enum  type= ReportType_Enum.WEB;
    /*报告搜索应该使用Isp.no来匹配
     * 为何还保留这个字段？？ 分项报告的编号允许和主报告的编码不一致吗？ 不是就可直接抛弃！ 还是用isp.no;
     * 实际上 Isp.no 可代替本字段, 一个Isp只能有一个证书或报告号码{证书特别序列号的:可以放入报告内容json或者其他字段}
     * 报告号，合格证编号。
     * 正常的，每个分项报告的编号都是直接采用母报告的报告号。
     * 制造监检，序列化合格证，代表发证的存档档案号。
     * 子分项某些报告若是外包完成的？也要补充内容：外包报告媒体形式/印刷。
     */
//    @Deprecated
//    private String  no;

    /** URI: 其它存取报告的实际URL; 物理报告存储形式，不一定都是Report中的json字段存储+前端报告模板方式；
     * 外部文件形式 Pdf, excel, docx, *image; 第三方的URL;
     * 目录形式的封面报告：{自包含 其它的分项目子报告的url，并且编排展示, 先有子报告，再加入母报告展示目录的列表链接}
     * 从外部或旧平台导入的报告,URI;
     * 旧平台模式对接的报告系统：PDF/Excel文件上传=URL。
     * Report实体还不是真正的物理含义的检验报告：{path/URI+type+data+snapshot+files}才是最终的物理含义报告内容给前端显示的。
     * 外部独立报告出具系统：需要标识显示内容URL。数据单方向流动，业务系统给报告系统预定义数据，报告系统可能要通过接口反馈业务系统做数据修改的。
     * 人工纸质的报告的存档说明。
     * 旧平台对于每个检验机构的如果可以默认推算出来URI的，过渡阶段数据整合实际可以不需要设置URI:path也可的。默认配置法。
     */
    private String path;

    /**单个Isp检验业务记录，可有很多份子报告，分项报告 报告类型可以不同的。
     单次ISP如果多个报告，每个报告单独打印，单独编制报告，单独链接；主报告1+N。
     分项报告=1份报告附带多份的子报告，子报告无需要独立的报告编号。依靠Isp业务来关联和指引组合显示。
     已经组合完成的{旧平台生成}报告path/URI直接指向组合后的总报告，就不需要分开流转。分项报告能够独立流转就需要Isp.reps+stm来控制。
    IspCold：冷数据=历史报告=已经失去关联意义的历史上的报告；历史报告及Isp可以单独存储。ES也能单独区分。查询和前端Report显示以及文件存储也能独立部署。
    本系统移除的旧数据旧报告，已经失去关联存在意义的Isp和报告附属数据和文件：从本系统删除并拷贝进入旧报告系统{旧报告存储以及按报告号码搜索过滤+报告内容前端显示}。
    初始化注意 @NotNull isp不可为空。
    对方对应的是 @OneToMany(mappedBy ="isp")   List<Report>  reps;含多个分项报告。
    */
    @QueryInit({"bus.task.servu", "report.stm.master","dev"})
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn
    private Isp isp;

    //下结论 完结日?
    private Date upLoadDate;

    /** 报告类型：有资质认证后才允许做这种报告，前端管理的字典，目前后端不关心具体类型的编码。
     * 走旧平台的报告的REP_TYPE版本可以不需要在这里配置的。
     * 报告模板的分类识别代码, "EL-DJ" "EL-JJ" 也都能体现出设备种类的。
     * 人员是否具备报告出具权限判断,只判断主报告; 主报告类型和用户权限控制+部门的角色。
     TB_TASK_TO_ISPPROJ分解 SUB_ISPID / REP_TYPE /主报告或者分项报告；
     REP_TYPE可重名不同号，？版本区别吗。
     能允许挑选：散装/电站/整装/组装/工业/锅炉_修理改造监督检验;
     TB_DICT_REPORTTYPE模板号,TB_DICT_REPORTTYPE_CFG设备选择报告，TB_DICT_REPORTTYPE_UPLOG多个版本的模板+END_TAG='1'。
     */
    private String  modeltype;
    /** 报告的版本，适配业务要求演化；
     * 模板的版本标识号
     */
    private Short  modelversion;

    /**原始记录，可录入可编辑部分 JSON。
      CLOB字段 会导致Hibernate无法自动创建该表？ 手动修改 建初始化表。
    原始记录内容-JSON；在前端录入和修改的部分。Oracle是这样@Column( columnDefinition="CLOB")
    字段长度，mysql8.0　数据库要修改定义成 clob;
     文档性质存储，文件操作=前端的交互。
    存储分离出去到MogoDB库？非关系数据库可能直接支持json的字段全部独立出来操作,字段灵活扩展，模型定义字段=data细化成为模型模板的诸多填充域。
    复检报告并未改写不合格内容而是添加模式。
    设备单位等相关信息必须快照固化进入data{}当中，存档资料就不能用最新动态的数据。 json直接的查询接口只能适配数据库MySQL;
    注意：主报告本json有两个特殊字段{"下检日期1":，"下检日期2"：}，  检验结论字段直接同时设置到Isp.conclusion就得使用单独命令操作。上报给监察平台时处理需看json{下检日期}字段。
     $不是WEB类型报告的，也可以允许有附加data存储的！
     */
    //@Type( type = "json" )
    @Column( columnDefinition = "jsonb" )
    private String  data;

    /**报告展示用到一部分数据-JSON，在编制后提交审核时就能固定化了。
     * 可直接复制合并到data，存snapshot仅是接口对接便利的过渡工具。
    纯粹是后端提供给检验报告的，编制报告的那一时间的相关设备状态数据【快照】。接口对接复制完成后就可清空了。
     前端实际上把 data+snapshot 两个进行合并。
    多份子报告由于时差设备快照不一致？没必要：子报告直接继承主报告本字段数据,物理存储上分项报告本字段=null。
    把columnDefinition="TEXT (64000)"改成columnDefinition = "json"，就多了约束；但是只能适应mySQL数据库啊，其它底层物理数据库可能不支持啊！。
     好处是：json当中属性假如遇见过滤取值需求呢：mysql就能方便地统计过滤，可以直接操作select * from report where data->'$.字段2' like '%数值%'这样的就能过滤。
     【特殊需求】可以应对：针对某个报告的某些特殊字段的取值进行过滤统计，Java后端以及前端不见得需要这个功能的，可是数据库维护和BI业务统计等特别系统模块也许需要该功能。
     */
    @Lob
    @Basic(fetch= FetchType.LAZY)
    @Column( columnDefinition="jsonb")
    private String  snapshot;
    //TEXT,MEDIUMTEXT,LONGTEXT三种不同类型，BLOB和TEXT大量删除操作性能有影响。建议定期使用OPTIMEIZE TABLE功能对表碎片整理。

    /*关联的 单线图等文件。 附属物理文件的实际存储期限可能受管控啊，不一致了；
     * 文件名路径实际在json当中保存image* file的url，而这里是关联对象，管理文件存储有效期,用户权限等。
     * 注意！ 存在不一致风险。
     * 子报告分项报告带来的附加上传文件。 web前端单线图模板视图: 顺序多个[<image>file</image>]，--附加字段/单元编号说明--data里面输入File的ID嵌入关联。
     关联File ID直接放置json中，后端不好处理{那个字段的json[[,]]}，文件更改ID不变还容易解释，删除File要解除锁定。
     单线图 还会修改的！ 这次报告和上一次报告的单线图 并不一定还是相同的。 多次检验报告同一个管道单元单线图可能修改过。
     走独立生命周期的对象存储 文件。json{_FILE[]}
     * */
//    @OneToMany(mappedBy="report")
//    private Set<File> files;          很难管理

    /**流程控制;   其它实体模型如审批单也能关联ApprovalStm字段，单向关联。
     * 如果null=默认=主报告报告和证书已发放完成或压缩固化后的。非集成web形式的外部独立报告出具系统也需要配套 流转控制的。
     * 报告终结之后，所有非核心内容比如ApprovalStm这种临时性质的对象，在过了一段时间期限后，应该自动删除压缩历史数据,可把部分数据固化掉给Isp对象来存储。
     * 【冷数据判定依据】 stm=null? 流程必定全搞完。 @报告Report初始化时stm必须非空的。
     * 主报告或子报告的审批流转状态机对象。
     * 独立出去对象，和其它审批单子的模型可以共用，避免关联关系定义上需要一个字段做映射关系到多个实体模型。
     * 多个检验员同一时间一起修改同一份报告的风险：数据修改后被其他人覆盖了，需要经常刷新取得最新的报告数据。
     * 针对那些旧报告比如：外部文件形式 Pdf, excel, docx, *image 第三方的URL;本字段stm可以=null表示缺省是=已终结报告！
     * 【报告的生成】报告编制，流转和审批，签名。
     * 确认收费清单+关联发票！！做完这些后才能结束报告编制状态！,流转责任审核。
     * 主动报检受理必须先开票？还没干活，先收钱?(开票=预备)
     * 冷数据过期后，stm可为空。【短期存储】热数据才有，报告终结预计2年以后过期删除stm关联对象。
     * stm是短期存在的！ Isp isp是长久存储; 而Detail和Task,流转记录和收费明细表等 也是短期存储的。
    */
    @QueryInit("master")
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn( referencedColumnName = "ID")
    private ApprovalStm  stm;

    /**相关文件的存储链接，可可能多种的,json{}形式，灵活点：
     * */
//  @Convert(converter = RepLink.LinkConverter.class)  // 关联转换器
//还有一种  @Type(type = JsonBinaryType.class) // 使用 Hibernate JSON 类型处理器
//配合 class RepLink的   @JsonProperty("ori")  private String originalLink;
    @Column(columnDefinition = "jsonb", name="link")
    private String link;
    public RepLink getLink() {
        return JSON.parseObject(link, RepLink.class);  // 读取时手动反序列化
    }
    public void setLink(RepLink link) {
        this.link = JSON.toJSONString(link);  // 保存时手动序列化
    }
    /**最近改的用户。
     * */
    @ManyToOne(fetch= FetchType.LAZY)
    private User modifyBy;
    /**最近改的用户设备标签。配套modifyBy来确定那个客户端做修改的
     * */
    @Column
    private String modifyDfp;


    public  Report(ReportType_Enum type, String no, Isp isp){
        this.type=type;
//        this.no=no;
        this.isp=isp;
        //初始化
        data="{}";
    }
    //重载是依靠参数类型以及个数和顺序来确定的。
    public  Report(String path, Isp isp, String no){
        this.path=path;
//        this.no=no;
        this.isp=isp;
        data="{}";
    }
    //graphql选择集　安全信息控制方式： 单个字段的。
    //@PostAuthorize对实体类不起作用，对行动类有效？    @PostAuthorize("hasRole('ADMIN')") !;
    /*public Date getUpLoadDate() {
        long rights = SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().filter(authority ->
                authority.equals(new SimpleGrantedAuthority("ROLE_cmnAdmin"))
                || authority.equals(new SimpleGrantedAuthority("ROLE_Ma"))  ).count();

        if(rights>0)
            return upLoadDate;
        else
            return null;      //这样就切断了graphQL选择集，前端无法查询该字段也无法嵌套。
        //没有登录的客人也有程序角色："ROLE_ANONYMOUS"；
    }
    */
    //JPA实体从数据库装入的实例化过程，根本就没有运行这个setter()/getter();
    /*@PreAuthorize("hasRole('ADMIN')")
    public void setUpLoadDate(Date upLoadDate) {

          this.upLoadDate = upLoadDate;
    }*/

    public String getId(DataFetchingEnvironment env) {
        return Tool.toGlobalId(this.getClass().getSimpleName(), this.id);
    }
}

/*
JPA @Query实现mysql中json字段查询; https://segmentfault.com/a/1190000039093497
@Query(value = "select * from item where json_extract(item_info,?1) =?2 and item_id in (?3)",nativeQuery = true)
List<Item> selectItems( String featuresKey,String featuresValue,List<Long> itemIds);
List<Item> items = itemRepository.selectItems("$.GoodType","cat",ids);
* */


/* @数据库修改脚本：
CREATE INDEX report_stm_id_idx ON public.report USING btree (stm_id ASC);
CREATE INDEX report_isp_id_index ON public.report USING btree (isp_id ASC);
* */